package controllers

import java.io._
import java.net.URL
import com.google.inject.Provides
import dao._

import javax.inject.Inject
import models.Tables.MissionRow
import org.apache.commons.io.FileUtils
import org.joda.time.{DateTime, Days}
import org.jxls.common.Context
import org.jxls.util.JxlsHelper
import play.api.libs.json.Json
import play.api.mvc.{AbstractController, ControllerComponents, RequestHeader}
import tool.{DivData, FileTool, FormTool, ImageInfo, MyLogger, Tool}
import utils.Utils
import models.Tables._
import org.apache.commons.lang3.StringUtils
import org.slf4j.MarkerFactory
import play.api
import play.api.{Logger, MarkerContext}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import implicits.Implicits._
import shared.Shared
import spdf._

import java.sql.Date

/**
 * Created by yz on 2018/12/12
 */
class PredictController @Inject()(cc: ControllerComponents, formTool: FormTool, tool: Tool,
                                  missionDao: MissionDao, extraDataDao: ExtraDataDao,
                                  trashDao: TrashDao) extends
  AbstractController(cc) {

  val logger = MyLogger(tool)(this.getClass())

  def predictBefore = Action { implicit request =>
    Ok(views.html.user.predict())
  }

  def batchPredictBefore = Action { implicit request =>
    Ok(views.html.user.batchPredict())
  }

  def predict = Action.async { implicit request =>
    val data = formTool.predictForm.bindFromRequest().get
    val userId = Tool.getUserId
    val myMessage = Tool.dataCheck(data).toMyMessage
    if (myMessage.valid) {
      val time = new Date(System.currentTimeMillis())
      val row = MissionRow(id = 0, userId = userId, sampleId = data.sampleId, name = data.name, age = data.age,
        ast = data.ast, alt = data.alt, plt = data.plt, tyr = data.tyr,
        tca = data.tca, endTime = time)
      missionDao.selectOp(row).flatMap { missionOp =>
        missionOp.map { mission =>
          val newRow = row.copy(id = mission.id)
          missionDao.update(newRow).map { x =>
            mission.id
          }
        }.getOrElse {
          missionDao.insertAndReturnId(row)
        }
      }.flatMap { missionId =>
        val extraRow = tool.getEmptyExtraDataRow(missionId, userId).copy(sampleId = data.sampleId, name = data.name,
          checkDate = Utils.dataTime2String(new DateTime()), interviewRecord = data.interviewRecord)
        val trashRow = TrashRow(missionId, userId, true)
        extraDataDao.insertOrUpdate(extraRow).zip(trashDao.insertOrUpdate(trashRow)).map { x =>
          val json = Json.obj("missionId" -> missionId)
          Ok(json)
        }
      }
    } else {
      Utils.result2Future(Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message)))
    }
  }

  def batchPredict = Action.async(parse.multipartFormData) { implicit request =>
    val tmpDir = tool.createTempDirectory("tmpDir")
    val inputFile = new File(tmpDir, "input.xlsx")
    val file = request.body.file("file").get
    file.ref.moveTo(inputFile, replace = true)
    val myMessage = FileTool.fileCheck(inputFile)
    if (myMessage.valid) {
      val userId = Tool.getUserId
      val maps = inputFile.xlsxLines().lineMap
      tool.deleteDirectory(tmpDir)
      val time = new Date(System.currentTimeMillis())
      val ts = maps.map { case map =>
        //        val randomInt = (math.random() * 365).toInt
        //        val randomDate = (new DateTime()).plusDays(randomInt)
        //        val time = new Date(randomDate.getMillis)
        val orinalData = tool.getOrinalData(map)
        val missionRow = MissionRow(0, userId = userId, sampleId = orinalData.sampleId, name = "", age = orinalData.age,
          ast = orinalData.ast, alt = orinalData.alt,
          plt = orinalData.plt, tyr = orinalData.tyr, tca = orinalData.tca, endTime = time)
        val extraData = tool.getExtraData(map)
        val checkDate = if (StringUtils.isEmpty(extraData.checkDate)) Utils.dataTime2String(new DateTime()) else extraData.checkDate
        val extraRow = tool.getEmptyExtraDataRow(0, userId).copy(sampleId = orinalData.sampleId, name = extraData.name,
          unit = extraData.unit, address = extraData.address, sex = extraData.sex, office = extraData.office,
          doctor = extraData.doctor, number = extraData.number, sampleTime = extraData.sampleTime, submitTime = extraData.submitTime,
          sampleType = extraData.sampleType, sampleStatus = extraData.sampleStatus,
          title = extraData.title, reporter = extraData.reporter, checker = extraData.checker, checkDate = checkDate,
          reportDate = extraData.reportDate
        )
        val trashRow = TrashRow(0, userId, true)
        (missionRow, extraRow, trashRow)
      }.distinctByKeepLast(x => (x._1.sampleId, x._1.endTime.toString))
      missionDao.selectAll(userId).flatMap { dbMissions =>
        val dbMap = dbMissions.map { x =>
          ((x.sampleId, x.endTime.toString) -> x)
        }.toMap
        val newTs = ts.map { x =>
          dbMap.get((x._1.sampleId, x._1.endTime.toString)).map { dbMission =>
            val newMissionRow = x._1.copy(id = dbMission.id)
            val newExtraRow = x._2.copy(id = dbMission.id)
            val newTrashRow = x._3.copy(id = dbMission.id)
            (newMissionRow, newExtraRow, newTrashRow)
          }.getOrElse {
            x
          }
        }
        missionDao.insertOrUpdates(newTs).flatMap { missionIds =>
          missionDao.selectByMissionIds(missionIds).zip(extraDataDao.selectByMissionIds(missionIds)).map { case (x, extraRows) =>
            val missionRows = x
            val extraMap = extraRows.map(x => (x.id, x)).toMap
            val t2s = missionRows.map(x => (x, extraMap.getOrElse(x.id, tool.getEmptyExtraDataRow(x.id, x.userId))))
            val array = Utils.getArrayByT2s(t2s)
            Ok(Json.obj("array" -> array, "valid" -> true))
          }
        }
      }
    } else {
      tool.deleteDirectory(tmpDir)
      Utils.result2Future(Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message)))
    }
  }

  def downloadExampleFile = Action { implicit request =>
    val data = formTool.fileNameForm.bindFromRequest().get
    val file = new File(Utils.path, s"example/${data.fileName}")
    Ok.sendFile(file).withHeaders(
      CACHE_CONTROL -> "max-age=3600",
      CONTENT_DISPOSITION -> s"attachment; filename=${
        file.getName
      }",
      CONTENT_TYPE -> "application/x-download"
    )
  }

  def exportExcel = Action { implicit request =>
    val data = formTool.basicInfoForm.bindFromRequest().get
    val file = new File(Utils.templateDir, s"live_forest_template.xlsx")
    val tmpDir = tool.createTempDirectory("tmpDir")
    val outFile = new File(tmpDir, "live_forest_out.xlsx")
    val is: InputStream = new FileInputStream(file)
    val os: OutputStream = new FileOutputStream(outFile)
    val context = new Context()
    context.putVar("basicInfo", data)
    JxlsHelper.getInstance().processTemplate(is, os, context)
    is.close()
    os.close()
    Ok.sendFile(outFile, onClose = () => {
      tool.deleteDirectory(tmpDir)
    }).withHeaders(
      CONTENT_DISPOSITION -> s"attachment; filename=${
        outFile.getName
      }",
      CONTENT_TYPE -> "application/x-download"
    )
  }

  def pdfTest = Action { implicit request =>
    val tmpDir = tool.createTempDirectory("tmpDir")
    val startTime = System.currentTimeMillis()
    val outFile = new File(tmpDir, "live_boost_out.pdf")
    val pdf = Pdf(new PdfConfig {
      orientation := Portrait
      pageSize := "A4"
      pageSize := "Letter"
      marginTop := "1in"
      marginBottom := "1in"
      marginLeft := "0in"
      marginRight := "0in"
    })
    pdf.run(views.html.user.htmlTest().toString(), outFile)

    Ok(Json.toJson("success!"))
  }

  def export = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = formTool.basicInfoForm.bindFromRequest().get
    val pdfData = FormTool.pdfDataForm.bindFromRequest().get
    val missionId = formTool.missionIdForm.bindFromRequest().get.missionId
    val tmpDir = tool.getUserIdDir(userId)
    val outPdfFile = new File(tmpDir, "live_forest_out.pdf")
    val pdf = Pdf(s"${tool.binDir}/wkhtmltopdf/bin/wkhtmltopdf.exe", new PdfConfig {
      orientation := Portrait
      pageSize := "A4"
      pageSize := "Letter"
      marginTop := "0.5in"
      marginBottom := "0in"
      marginLeft := "0.5in"
      marginRight := "0.5in"
      title := "LiveFibAS PC"
    })
    missionDao.selectByMissionId(missionId).flatMap { row =>
      val missionIdDir = tool.getMissionIdDirById(missionId)
      val outFile = new File(missionIdDir, "out.txt")
      val logoFile = new File(tool.dataDir, "logo.png")
      val logoBase64 = logoFile.base64
      val qrBase64 = new File(tool.dataDir, "qr_code.jpg").base64
      val imageMap = Map("image0" -> pdfData.base640, "image1" -> pdfData.base641, "image2" -> pdfData.base642,
        "image3" -> pdfData.base643, "image4" -> pdfData.base644, "image5" -> pdfData.base645)
      val imageInfo = imageMap ++ Map("logo" -> logoBase64, "qrCode" -> qrBase64)
      val htmlFile = new File(tmpDir, "out.html")
      val html = views.html.user.html(data, row, imageInfo, pdfData).toString()
      html.toFile(htmlFile)
      pdf.run(htmlFile, outPdfFile)
      val bytes = FileUtils.readFileToByteArray(outPdfFile)
      val extraDataRow = ExtraDataRow(id = row.id, userId = row.userId, sampleId = data.sample, unit = data.unit,
        address = data.address, name = data.name, sex = data.sex,
        office = data.office, doctor = data.doctor, number = data.number, sampleTime = data.sampleTime,
        submitTime = data.submitTime, sampleType = data.sampleType, sampleStatus = data.sampleStatus,
        title = data.title, interviewRecord = data.interviewRecord, reporter = data.reporter, checker = data.checker,
        checkDate = data.checkDate, reportDate = data.reportDate, diagnosisConclusion = data.diagnosisConclusion)
      extraDataDao.insertOrUpdate(extraDataRow).map { x =>
        Redirect(s"${routes.PredictController.exportPdf}?dir=${tmpDir.getAbsolutePath.urlEncode}")
      }
    }
  }

  def exportPdf = Action { implicit request =>
    val data = FormTool.dirForm.bindFromRequest().get
    val tmpDir = new File(data.dir)
    val outPdfFile = new File(tmpDir, "live_forest_out.pdf")
    val bytes = FileUtils.readFileToByteArray(outPdfFile)
    Ok(bytes).as("application/pdf")
  }

  def toHtml = Action { implicit request =>
    Ok(views.html.user.htmlTest())
  }

  def predictResult = Action.async { implicit request =>
    val data = formTool.missionIdForm.bindFromRequest().get
    missionDao.selectByMissionId(data.missionId).zip(extraDataDao.selectByMissionId(data.missionId)).map { case (row, extraRow) =>
      val extraDataJson = Utils.getJsonByT(extraRow)
      val missionJson = tool.getMissionJson(row)
      val json = Json.obj("extraData" -> extraDataJson, "mission" -> missionJson)
      Ok(json)
    }
  }

  def getLinearPlotData = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val dateData = FormTool.dateForm.bindFromRequest().get
    missionDao.selectByMissionId(data.missionId).flatMap { curMission =>
      missionDao.selectAll(curMission.sampleId, curMission.userId).map { tmpAllMissions =>
        val allMissions = tmpAllMissions.filter { x =>
          val curDate = new DateTime(x.endTime.getTime)
          val startB = dateData.startDate.flatMap { date =>
            Utils.string2DateTimeOp(date, "yyyy-MM-dd")
          }.map { dateTime =>
            dateTime.isBefore(curDate) || dateTime.isEqual(curDate)
          }.getOrElse(true)
          val endB = dateData.endDate.flatMap { date =>
            Utils.string2DateTimeOp(date, "yyyy-MM-dd")
          }.map { dateTime =>
            dateTime.isAfter(curDate) || dateTime.isEqual(curDate)
          }.getOrElse(true)
          startB && endB
        }
        val index = allMissions.indexOf(curMission)
        val dates = allMissions.map(_.endTime.toString)
        val ages = allMissions.map(_.age)
        val asts = allMissions.map(_.ast)
        val alts = allMissions.map(_.alt)
        val plts = allMissions.map(_.plt)
        val tyrs = allMissions.map(_.tyr)
        val tcas = allMissions.map(_.tca)
        val dTickN = 5
        val oneDay = 86400000L
        val dTick = if (allMissions.nonEmpty) {
          val startDate = new DateTime(allMissions.last.endTime.getTime)
          val endDate = new DateTime(allMissions.head.endTime.getTime)
          val dateRange = Days.daysBetween(startDate, endDate)
          math.max(dateRange.getDays / dTickN, 1) * oneDay
        } else {
          oneDay
        }
        val ageDTick = if (allMissions.nonEmpty) {
          val agesInt = ages.map(_.toInt)
          val range = agesInt.max - agesInt.min
          s"${math.max((range + 1) / 4, 1)}"
        } else {
          "1"
        }
        val finalDTick = dTick match {
          case x if x > oneDay * 365 =>
            val year = x / (oneDay * 365)
            s"Y${year}"
          case x if x > oneDay * 30 =>
            val month = x / (oneDay * 30)
            s"M${month}"
          case x => x.toString
        }

        val colors = allMissions.indices.map { x =>
          if (x == index) "red" else "#1F77B4"
        }
        val array = List(
          Json.obj("x" -> dates, "y" -> ages, "title" -> "年龄-时间变化曲线", "yTitle" -> "岁"),
          Json.obj("x" -> dates, "y" -> asts, "title" -> "AST浓度-时间变化曲线", "yTitle" -> "U/L"),
          Json.obj("x" -> dates, "y" -> alts, "title" -> "ALT浓度-时间变化曲线", "yTitle" -> "U/L"),
          Json.obj("x" -> dates, "y" -> plts, "title" -> "PLT浓度-时间变化曲线", "yTitle" -> "10^9/L"),
          Json.obj("x" -> dates, "y" -> tyrs, "title" -> "Tyr浓度-时间变化曲线", "yTitle" -> "μmol/L"),
          Json.obj("x" -> dates, "y" -> tcas, "title" -> "TCA浓度-时间变化曲线", "yTitle" -> "ng/mL"),
        )
        val json = Json.obj("array" -> Json.toJson(array), "dTick" -> finalDTick, "colors" -> colors,
          "ageDTick" -> ageDTick)
        Ok(json)
      }
    }
  }


}
